<template>
  <h4>{{ title }}</h4>
  <canvas :id="key" class="c2d" width="1000" height="500"></canvas>
</template>

<script setup>
import { ref, onMounted } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const key = ref("c2d" + Math.floor(Math.random() * 10000));
const props = defineProps({
  title: String,
});

onMounted(() => {
  const canvas = document.querySelector(`#${key.value}`);
  // 渲染器
  const renderer = new THREE.WebGLRenderer({ canvas });

  const fov = 40; // 视野范围
  const aspect = 2; // 相机默认值 画布的宽高比
  const near = 0.1; // 近平面
  const far = 10000; // 远平面
  // 透视投影相机
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  // 设置相机 x、y、z 位置
  camera.position.set(0, 100, 500);
  // 相机指向的场景中心
  camera.lookAt(0, 0, 0);
  // 控制相机
  const controls = new OrbitControls(camera, canvas);
  controls.update();
  // 场景
  const scene = new THREE.Scene();

  const lines = [];
  let positions = null;
  let opacitys = null;
  const geometry = new THREE.BufferGeometry();
  // 以北京为中心 修改坐标
  const projection = d3
    .geoMercator()
    .center([116.412318, 39.909843])
    .translate([0, 0]);

  const loader = new THREE.FileLoader();
  loader.load("src/assets/file/100000.json", (data) => {
    const jsondata = JSON.parse(data);

    // 中国边界
    const feature = jsondata.features[0];
    const province = new THREE.Object3D();
    province.properties = feature.properties.name;
    // 点数据
    const coordinates = feature.geometry.coordinates;

    coordinates.forEach((coordinate) => {
      // coordinate 多边形数据
      coordinate.forEach((rows) => {
        const line = lineDraw(rows, 0xffffff);
        province.add(line);
      });
    });

    positions = new Float32Array(lines.flat(1));
    // 设置顶点
    geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
    // 设置 粒子透明度为 0
    opacitys = new Float32Array(positions.length).map(() => 0);
    geometry.setAttribute("aOpacity", new THREE.BufferAttribute(opacitys, 1));

    scene.add(province);
  });

  let indexBol = true;
  /**
   * 边框 图形绘制
   * @param polygon 多边形 点数组
   * @param color 材质颜色
   * */
  function lineDraw(polygon, color) {
    const lineGeometry = new THREE.BufferGeometry();
    const pointsArray = new Array();
    polygon.forEach((row) => {
      const [x, y] = projection(row);
      // 创建三维点
      pointsArray.push(new THREE.Vector3(x, -y, 0));

      if (indexBol) {
        lines.push([x, -y, 0]);
      }
    });
    indexBol = false;
    // 放入多个点
    lineGeometry.setFromPoints(pointsArray);

    const lineMaterial = new THREE.LineBasicMaterial({
      color: color,
    });
    return new THREE.Line(lineGeometry, lineMaterial);
  }

  // 控制 颜色和粒子大小
  const params = {
    pointSize: 2.0,
    pointColor: "#4ec0e9",
  };

  const vertexShader = `
        attribute float aOpacity;
        uniform float uSize;
        varying float vOpacity;

        void main(){
            gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
            gl_PointSize = uSize;

            vOpacity=aOpacity;
        }
        `;

  const fragmentShader = `
        varying float vOpacity;
        uniform vec3 uColor;

        float invert(float n){
            return 1.-n;
        }

        void main(){
          if(vOpacity <=0.2){
              discard;
          }
          vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
          vec2 cUv=2.*uv-1.;
          vec4 color=vec4(1./length(cUv));
          color*=vOpacity;
          color.rgb*=uColor;
          gl_FragColor=color;
        }
        `;
  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true, // 设置透明
    uniforms: {
      uSize: {
        value: params.pointSize,
      },
      uColor: {
        value: new THREE.Color(params.pointColor),
      },
    },
  });
  const points = new THREE.Points(geometry, material);
  scene.add(points);

  let currentPos = 0;
  let pointSpeed = 20; // 速度
  // 渲染
  function render() {
    if (points && geometry.attributes.position) {
      currentPos += pointSpeed;
      for (let i = 0; i < pointSpeed; i++) {
        opacitys[(currentPos - i) % lines.length] = 0;
      }

      for (let i = 0; i < 200; i++) {
        opacitys[(currentPos + i) % lines.length] = i / 50 > 2 ? 2 : i / 50;
      }
      geometry.attributes.aOpacity.needsUpdate = true;
    }

    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
});
</script>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>
